package org.mika.asm;

import java.lang.reflect.Method;
import java.util.HashMap;

import net.sf.cglib.asm.ClassWriter;
import net.sf.cglib.asm.Type;
import net.sf.cglib.core.ClassEmitter;
import net.sf.cglib.core.CodeEmitter;
import net.sf.cglib.core.EmitUtils;
import net.sf.cglib.core.GeneratorStrategy;
import net.sf.cglib.core.ReflectUtils;
import org.mika.asm.MethodUtils;

final class MethodsOpenerGenerator implements GeneratorStrategy {		
	private Class<?> clazz, iface;
	
	public MethodsOpenerGenerator(Class<?> clazz, Class<?> iface) {
		this.clazz = clazz;
		this.iface = iface;
	}
	
	public static Object invoke$private$(Object target, String m, Object[]args) {
		throw new NotImplementedException("Method invoke$private$ is inaccessible within this context");
	}	
		
	@Override
	public byte[] generate(net.sf.cglib.core.ClassGenerator cg) throws Exception {
		ClassWriter  writer  = new ClassWriter(true);
		ClassEmitter ce = new ClassEmitter(writer);
		
		ASMUtils.generateOpenerHeader (ce, clazz, iface);
		ASMUtils.generateConstructors (ce, clazz);
		ASMUtils.declareHashMap       (ce, MethodUtils.METHODS);		
		MethodUtils.generateStatic    (ce, clazz, iface);		
		
		/*
		public static Object invoke$private$(Object o, String methodId, Object[] args) {
			return $$methods.get(methodId).invoke(o, args);
		}
		*/		
		{ 
			CodeEmitter e = EmitUtils.begin_method(
				ce, ReflectUtils.getMethodInfo(
					MethodsOpenerGenerator.class.getMethod(MethodUtils.INVOKE_METHOD, new Class<?>[] {
						Object.class, String.class, Object[].class
					})
				)
			);
			
			e.getstatic( ce.getClassType(), MethodUtils.METHODS, Type.getType(HashMap.class) );
			e.load_arg(1);
			
			e.invoke(
				ReflectUtils.getMethodInfo(
					HashMap.class.getMethod("get", new Class[]{ Object.class })
				)
			);
			
			e.checkcast(Type.getType( Method.class ));
						
			e.load_arg(0);
			e.load_arg(2);
			
			e.invoke(
				ReflectUtils.getMethodInfo(
					Method.class.getMethod("invoke", new Class[]{ Object.class, Object[].class })
				)
			);
			
			e.return_value();
			e.end_method();
		}
		
		ce.end_class();
		
		return writer.toByteArray();
	}
}
